<?php

namespace App\Http\Controllers\Payment;

use App\Http\Controllers\Controller;
use App\Jobs\PaymentOrderPaid;
use App\Models\Common\ApiResponse;
use App\Models\Common\Constant;
use App\Models\Coupon\CouponOrder;
use App\Models\Coupon\CouponOrderPayUtils;
use App\Models\Merchant\MerchantOrder;
use App\Models\Merchant\MerchantOrderPayUtils;
use App\Models\ParkingLot\MthOrder;
use App\Models\ParkingLot\MthOrderPayUtils;
use App\Models\ParkingLot\ParkingLot;
use App\Models\ParkingLot\ParkOrder;
use App\Models\ParkingLot\ParkOrderPayUtils;
use App\Models\Payment\PaymentChannel;
use App\Models\Payment\PaymentOrder;
use App\Models\Payment\PaymentScene;
use App\Models\Payment\Platform\EbankPay;
use App\Models\Payment\Platform\FuiouAggregatePay;
use App\Models\Payment\Platform\FuiouPay;
use App\Models\Payment\TradeType;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class ChannelController extends Controller
{

    public $name = '支付通道';

    public function index()
    {
//        $this->admin = $user = admin();
        $parkingLotID = getParkingLotID();
//        $this->logContent = '查看支付通道列表';
        return ApiResponse::return('', PaymentChannel::where('parking_lot_id', $parkingLotID)->get());
    }

    public function store(Request $request)
    {
        $this->admin = $user = admin();
        $this->parkingLotID = $parkingLotID = getParkingLotID();
        $this->logContent = '编辑支付通道';

        if ($channelID = $request->get('id')) {
            $paymentChannel = PaymentChannel::query()->where('parking_lot_id', $parkingLotID)->findOrFail($channelID);
        } else {
            $paymentChannel = new PaymentChannel();
            $paymentChannel->id = Str::orderedUuid()->toString();
            $paymentChannel->parking_lot_id = $parkingLotID;
        }

        $paymentChannel->fill($request->only(['category', 'name', 'parking_lot_id', 'disabled', 'remark']));

        if (!$paymentChannel->name) {
            return ApiResponse::fail('支付通道名称不可为空');
        }

        ParkingLot::findOrFail($paymentChannel->parking_lot_id);
        $parameters = $request->get('parameters');

        if ($parameters) {
            if (isset($parameters['key_file'])) {

                if ($parameters['key_file']) {
                    $paymentChannel->key_file = encrypt($parameters['key_file']);
                } else {
                    unset($parameters['key_file']);
                    $paymentChannel->key_file = '';
                }
            }

            if (isset($parameters['cert_file'])) {

                if ($parameters['cert_file']) {
                    $paymentChannel->cert_file = encrypt($parameters['cert_file']);
                } else {
                    unset($parameters['cert_file']);
                    $paymentChannel->cert_file = '';
                }
            }

            switch ($paymentChannel->category) {
                case PaymentChannel::CATEGORY_CHINAUMS:
                case PaymentChannel::CATEGORY_UNIONPAY_APP:
                case PaymentChannel::CATEGORY_UNIONPAY_ALIPAY:
                case PaymentChannel::CATEGORY_UNIONPAY_WECHATPAY:
                case PaymentChannel::CATEGORY_UNIONPAY_WECHATPAY_MINI:
                case PaymentChannel::CATEGORY_FUIOUPAY:
                case PaymentChannel::CATEGORY_FUIOUPAY_AGGREGATE:
                case PaymentChannel::CATEGORY_EBANKPAY:

                    $rule = \Arr::get(PaymentChannel::RULES, $paymentChannel->category);
                    $parameters = \Arr::only($parameters, array_keys($rule));
                    $parameters['gateway'] = \Arr::get($parameters, 'gateway') ?: '';
                    $validator = \Validator::make($parameters, $rule);

                    if ($validator->fails()) {
                        return ApiResponse::fail($validator->errors()->first(), ApiResponse::CODE_INVALID_ARGUMENT);
                    }

                    if (PaymentChannel::CATEGORY_EBANKPAY === $paymentChannel->category) {
                        if (!$rsaPriCertPwd = trim(\Arr::get($parameters, 'rsa_pri_cert_pwd'))) {
                            return ApiResponse::fail('私钥解密密码不可为空');
                        }
                        // 校验证书文件
                        // 私钥
                        $rsaPriCertPath = storage_path(\Arr::get($parameters, 'rsa_pri_cert_path'));// 私钥证书路径
                        // 读取私钥证书内容
                        $result = openssl_pkcs12_read(file_get_contents($rsaPriCertPath), $cert, $rsaPriCertPwd);
                        if (!$result) {
                            return ApiResponse::fail('读取私钥证书内容失败,请检查证书');
                        }
                        // 公钥
                        $rsaPubCertPath = storage_path(\Arr::get($parameters, 'rsa_pub_cert_path'));// 公钥证书路径
                        // 读取.CER文件的二进制内容
                        $rsaPubCertData = file_get_contents($rsaPubCertPath);
                        if (false === mb_stripos($rsaPubCertData, 'BEGIN CERTIFICATE')) {
                            // 将二进制数据转换为PEM格式
                            $rsaPubCertPemData = "-----BEGIN CERTIFICATE-----\n" .
                                chunk_split(base64_encode($rsaPubCertData), 64, "\n") .
                                "-----END CERTIFICATE-----\n";
                        } else {
                            $rsaPubCertPemData = $rsaPubCertData;
                        }
                        // 从证书内容创建X509对象
                        $x509 = openssl_x509_read($rsaPubCertPemData);
                        // 从X509对象中获取公钥资源
                        $pubKeyResource = openssl_pkey_get_public($x509);
                        // 如果需要，可以将公钥资源转换为字符串形式
                        if (!$pubKeyResource) {
                            return ApiResponse::fail('读取私钥证书内容失败,请检查证书');
                        }
                        // 清理资源
                        openssl_pkey_free($pubKeyResource);

                        // 清除支付证书缓存
                        $rsaPubCacheKey = 'paymentChannel-rsaPub:' . $paymentChannel->id;
                        $rsaPriCacheKey = 'paymentChannel-rsaPri:' . $paymentChannel->id;
                        \Cache::delete($rsaPubCacheKey);
                        \Cache::delete($rsaPriCacheKey);
                    }

                    if (PaymentChannel::CATEGORY_FUIOUPAY === $paymentChannel->category) {
                        if(strpos($parameters['gateway'], 'http') === false){
                            return ApiResponse::fail('接口域名以http或https开头');
                        }
                        $parameters['gateway'] = trim(trim($parameters['gateway']), '/');
                        $parameters['rsa_private_key'] = preg_replace("/\n/", "", $parameters['rsa_private_key']);
                    }

                    if (PaymentChannel::CATEGORY_FUIOUPAY_AGGREGATE === $paymentChannel->category) {
                        if(strpos($parameters['gateway'], 'http') === false){
                            return ApiResponse::fail('接口域名以http或https开头');
                        }
                        $parameters['gateway'] = trim(trim($parameters['gateway']), '/');
                    }

                    $paymentChannel->parameters = $parameters;
                    break;
                default:
                    return ApiResponse::fail('未启用的支付通道类型');
            }

            if ($validator->fails()) {
                throw new \InvalidArgumentException($validator->errors()->first());
            }
        } else {
            if (!$channelID) {
                return ApiResponse::fail('支付参数不可为空');
            }
        }

        if ($paymentChannel->save()) {
            return ApiResponse::return();
        }

        return ApiResponse::return('', null, ApiResponse::RESP_CODE_FAILURE);
    }

    public function show($id)
    {
//        $this->admin = $user = admin();
        $parkingLotID = getParkingLotID();
//        $this->logContent = '查看支付通道详情';
        $paymentChannel = PaymentChannel::where('parking_lot_id', $parkingLotID)->findOrFail($id);
        return ApiResponse::return('', $paymentChannel);
    }

    public function destroy($id)
    {
        $this->admin = $user = admin();
        $this->parkingLotID = $parkingLotID = getParkingLotID();
        $this->logContent = '删除支付通道';
        if (\DB::transaction(function () use ($parkingLotID, $id) {
            PaymentChannel::where('parking_lot_id', $parkingLotID)->where('id', $id)->delete();
            PaymentScene::where('parking_lot_id', $parkingLotID)->where('channel_id', $id)->delete();
            return true;
        })) {
            return ApiResponse::return('');
        }
        return ApiResponse::return('', null, ApiResponse::RESP_CODE_FAILURE);
    }

    public function getAvailableChannels(Request $request)
    {
        $this->needLogging = false;
        $user = admin();
        $parkingLotID = getParkingLotID(true);
        $scene = $request->get('scene') ?: PaymentScene::SCENE_TMP_PAYMENT;
        $source = $request->get('source'); // ALIPAY、WECHAT、WECHAT_MINI、MWEB

//        \Log::info('可用支付通道'.$scene.$parkingLotID);

        $baseQuery = PaymentScene::with(['paymentChannel'])->where('name', $scene)->where('parking_lot_id', $parkingLotID)->whereHasIn('paymentChannel', function ($childQuery) {
            $childQuery->where('disabled', false);
        });

        $channelDatPacket = [
            PaymentScene::SOURCE_MWEB => [],
            PaymentScene::SOURCE_MINI => []
        ];

//        switch ($request->get('scene')) {
//            case PaymentScene::SCENE_MONTHLY_PAYMENT:
//                $scene = PaymentScene::SCENE_MONTHLY_PAYMENT;
//                break;
//            case PaymentScene::SCENE_TMP_PAYMENT:
//            default:
//                $scene = PaymentScene::SCENE_TMP_PAYMENT;
//                break;
//        }

        foreach ($baseQuery->where('name', $scene)->get() as $paymentScene) {

            if (\Arr::has($channelDatPacket, $paymentScene->source) &&
                ($paymentChannel = $paymentScene->paymentChannel)) {

                $trade_types = PaymentChannel::TRADE_TYPES[$paymentChannel->category] ?? [];
                switch ($source) {
                    case 'ALIPAY':
                        if (!in_array(TradeType::ALI_JSAPI, $trade_types)) {
                            break;
                        }
                        break;
                    case 'WECHAT':
                        if (!in_array(TradeType::WECHAT_JSAPI, $trade_types) && in_array(TradeType::WECHAT_JSAPI_MWEB, $trade_types)) {
                            break;
                        }
                        break;
                    case 'WECHAT_MINI':
                        if (!in_array(TradeType::WECHAT_MINI, $trade_types)) {
                            break;
                        }
                        break;
                }

                $channelDatPacket[$paymentScene->source][] = [
                    'id' => $paymentChannel->id,
                    'name' => $paymentChannel->name,
                    'trade_types' => PaymentChannel::TRADE_TYPES[$paymentChannel->category] ?? [],
                    'tags' => PaymentChannel::TAGS[$paymentChannel->category] ?? [],
                    'sequence' => $paymentScene->sequence
                ];
            }
        }

        $allChannels = [];
        foreach ($channelDatPacket as &$rowChannelDat) {
            $rowChannelDat = collect($rowChannelDat)->sortBy('sequence')->toArray();
            $allChannels = array_merge($allChannels, $rowChannelDat);
        }

        if (!$allChannels) {
            throw new \InvalidArgumentException('车场未配置支付通道');
        }

        return $channelDatPacket;
    }

    public function tradePay(Request $request)
    {
        $this->needLogging = false;
        $parkingLotID = getParkingLotID(true);
        $data = \Arr::only($request->post(), ['scene', 'order_code', 'final_amount', 'channel_id', 'trade_type', 'openid']);

//        \Log::info('发起支付', $data);

        $channelID = \Arr::get($data, 'channel_id');
        $scene = \Arr::get($data, 'scene');

        if ($openid = \Arr::get($data, 'openid')) {
            if (!is_string($openid)) {
                throw new \InvalidArgumentException('openid参数格式错误');
            }
        }

        if (!$channel = PaymentChannel::find($channelID)) {
            throw new \InvalidArgumentException('支付通道不存在');
        }
        if ($channel->disable) {
            throw new \InvalidArgumentException('支付通道未启用');
        }

        if (!PaymentScene::where([
            'parking_lot_id' => $parkingLotID,
            'name' => $scene,
            'channel_id' => $channelID,
        ])->exists()) {
            throw new \InvalidArgumentException('支付场景未配置支付通道');
        }

        try {
            switch ($scene) {
                case PaymentScene::SCENE_MONTHLY_PAYMENT:
                    $appUser = app_user();
                    $responseData = MthOrderPayUtils::appPay($channel, $data, $appUser);
                    break;
                case PaymentScene::SCENE_TMP_PAYMENT:
                case PaymentScene::SCENE_MERCHANT_TMP_PAYMENT:
                    $appUser = app_user();
                    $responseData = ParkOrderPayUtils::appPay($channel, $data, $appUser);
                    break;
                case PaymentScene::SCENE_MERCHANT_PAYMENT:
                    $responseData = MerchantOrderPayUtils::appPay($channel, $data);
                    break;
                case PaymentScene::SCENE_COUPON_PAYMENT:
                    $responseData = CouponOrderPayUtils::appPay($channel, $data);
                    break;
                default:
                    throw new \InvalidArgumentException('支付场景错误');
            }
            return ApiResponse::return('', $responseData);
        } catch (\Throwable $exception) {
            \Log::warning('发起支付失败: '.$exception->getMessage(), ['query' => $data, 'exception' => [
                'file' => $exception->getFile(),
                'line' => $exception->getLine()
            ]]);
            throw new \InvalidArgumentException($exception->getMessage());
        }
    }

    public function payResult(Request $request)
    {
        $scene = $request->get('scene');
        $orderCode = $request->get('order_code');

        $orderInfo = [
            'order_status' => ''
        ];

        $order = null;
        switch ($scene) {
            case PaymentScene::SCENE_MONTHLY_PAYMENT:
                $order = MthOrder::where('order_code', $orderCode)->first();
                break;
            case PaymentScene::SCENE_TMP_PAYMENT:
            case PaymentScene::SCENE_MERCHANT_TMP_PAYMENT:
                $order = ParkOrder::where('order_code', $orderCode)->first();
                break;
            case PaymentScene::SCENE_MERCHANT_PAYMENT:
                $order = MerchantOrder::where('order_code', $orderCode)->first();
                break;
            case PaymentScene::SCENE_COUPON_PAYMENT:
                $order = CouponOrder::where('order_code', $orderCode)->first();
                break;
        }
        if (!$order) {
            return ApiResponse::return('', $orderInfo);
        }

        if (Constant::ORDER_STATUS_PAID === $order->order_status) {
            $orderInfo['order_status'] = $order->order_status;
            return ApiResponse::return('', $orderInfo);
        }
        if (!$order->paid_code) {
            return ApiResponse::return('', $orderInfo);
        }
        if (!$paymentOrder = PaymentOrder::where('paid_code', $order->paid_code)->first()) {
            return ApiResponse::return('', $orderInfo);
        }
        if (!$paymentChannel = PaymentChannel::find($paymentOrder->payment_channel_id)) {
            return ApiResponse::return('', $orderInfo);
        }

        $paySuccess = false;
        try {
            \DB::beginTransaction();

            switch (optional($paymentChannel)->category) {
                case PaymentChannel::CATEGORY_EBANKPAY:
                    $response = (new EbankPay($paymentChannel))->toQuery($paymentOrder);
                    if ((\Arr::get($response, 'result_code') === 'SUCCESS') && (\Arr::get($response, 'return_code') === 'SUCCESS')) {
                        // 支付成功
                        if ('03' === Arr::get($response, 'uop_data.orderInfo.orderStatus')) {
                            $outTransactionID = Arr::get($response, 'uop_data.orderInfo.sysSeqNum');

                            $paymentOrder->paid_at = now()->toDateTimeString();
                            $paymentOrder->out_trade_no = $outTransactionID;
                            $paymentOrder->frozen_balance_amount = 0;
                            $paymentOrder->frozen_point = 0;
                            $paymentOrder->order_status = Constant::ORDER_STATUS_PAID;

                            $paymentOrder->save();
                            $paySuccess = true;
                        }
                    }
                    break;
                case PaymentChannel::CATEGORY_FUIOUPAY:
                    $response = (new FuiouPay($paymentChannel))->toQuery($paymentOrder);
                    if ((\Arr::get($response, 'result_code') === 'SUCCESS') && (\Arr::get($response, 'return_code') === 'SUCCESS')) {
                        // 支付成功
                        if ('SUCCESS' === Arr::get($response, 'trans_stat')) {
                            $outTransactionID = Arr::get($response, 'transaction_id');

                            $paymentOrder->paid_at = now()->toDateTimeString();
                            $paymentOrder->out_trade_no = $outTransactionID;
                            $paymentOrder->frozen_balance_amount = 0;
                            $paymentOrder->frozen_point = 0;
                            $paymentOrder->order_status = Constant::ORDER_STATUS_PAID;

                            $paymentOrder->save();
                            $paySuccess = true;
                        }
                    }
                    break;
                case PaymentChannel::CATEGORY_FUIOUPAY_AGGREGATE:
                    $response = (new FuiouAggregatePay($paymentChannel))->toQuery($paymentOrder);
                    if ((\Arr::get($response, 'result_code') === 'SUCCESS') && (\Arr::get($response, 'return_code') === 'SUCCESS')) {
                        // 支付成功
                        if ('SUCCESS' === Arr::get($response, 'trans_stat')) {
                            $outTransactionID = Arr::get($response, 'transaction_id');

                            $paymentOrder->paid_at = now()->toDateTimeString();
                            $paymentOrder->out_trade_no = $outTransactionID;
                            $paymentOrder->frozen_balance_amount = 0;
                            $paymentOrder->frozen_point = 0;
                            $paymentOrder->order_status = Constant::ORDER_STATUS_PAID;

                            $paymentOrder->save();
                            $paySuccess = true;
                        }
                    }
                    break;
                default:
                    break;
            }

            \DB::commit();
        } catch (\Exception $exception) {
            \DB::rollBack();
        } finally {
            if ($paySuccess) {
                //处理后续的各类系统订单逻辑
                PaymentOrderPaid::dispatch($paymentOrder)->onQueue(Constant::QUEUE_PAYMENT);
            }
        }

        $orderInfo['order_status'] = $order->order_status;
        return ApiResponse::return('', $orderInfo);
    }
}
